已經快到鐵人賽的結尾了,但是我現在才想到我好像少講了一個東西,就是關於傳值與傳參考的部分,所以這一篇就來聊一下關於傳值與傳參考吧。
首先先簡單聊一下純粹的傳值,在 JavaScript 中我們知道原始型別是單純傳遞一個值給另一個變數,因此修改時並不會影響原有的變數:
var a = 'Ray';
var b = a;
b = 'QQ';
console.log(a, b); // Ray, QQ
那話題拉回到 Python 中,Python 與 JavaScript 也是相同的嗎?我們可以直接來看一次範例:
a = 'Ray'
b = a
b = 'QQ'
print(a, b) # Ray, QQ
從結果來看,我們可以看到 Python 與 JavaScript 的結果是一樣的。
但是如果今天採用的是另一種方式來呈現的話結果會相同嗎?這邊就讓我們來看看另一種範例程式碼:
def fn(x, y):
cache = 100
x = cache
y = x
x = 10
y = 20
fn(x, y)
print(x, y) # 10, 20
我們可以看到在上面的範例程式碼中,我將全域的變數 x
與 y
傳入到 fn
函式內,接下來在 fn
中宣告一個 cache = 100
的變數,而後面我做了重新賦予 x
與 y
的行為,但是你會發現 fn
內的調整並不會影響全域變數的結果,這代表著什麼呢?代表著我們只是傳遞值 (x
, y
) 給 fn
函式的參數使用,因此函式內部的變化它並不會影響原本的變數。
或許這個時候你會很聰明的想到我在「從 JavaScript 角度學 Python(6) - 變數作用域」章節中有介紹一個「global
」的方式可以針對外部變數做一些ㄌ調整,但是基本上如果你在這邊使用 global
方法的話是會出現錯誤的:
def fn(x, y):
cache = 100
global x, y # SyntaxError: name 'x, y' is parameter and global
x = cache
y = x
x = 10
y = 20
fn(x, y)
print(x, y) # 10, 20
而會出現「SyntaxError: name 'x, y' is parameter and global
」這一段錯誤訊息的主要原因在於,函式的參數如果與 global
指定的變數相同的話,是會無法正常運作的,因為它會不知道你到底是要使用哪一個參數還是變數唷~
所以我們這邊可以大膽的推測...
「或許 Python 的數值型別、字串型別與布林型別會與 JavaScript 的原始型別類似?」
那麼關於這個推測我們可以試著實踐一次或許會更精準,透過前面幾個章節我們了解到容器型別類似於 JavaScript 的陣列與物件,所以這邊我們可以試著撰寫字串、布林與數值型別來測試看看:
def fn(w, x, y, z):
w = False
x = 10
y = 1.1
z = 'Ray'
print(w, x, y, z) # False, 10, 1.1, Ray
w = True # 布林
x = 9.9 # 浮點數
y = 'Ray' # 字串
z = 10 # 整數
fn(w, x, y, z)
print(w, x, y, z) # True, 9.9, Ray, 10
透過上面的結果來看,基本上 Python 的字串、布林與數值型別確實是與 JavaScript 的基本型別雷同,那字典與串列呢?別急,我們先接著下去看,但是這邊要注意我們是在講 Python 不是 JavaScript:
接下來聊一下剛剛沒有提到的字典與串列的部分,雖然我們知道 Python 的字典與串列就是對應著 JavaScript 的物件與陣列,但實際上運作模式會是一樣嗎?所以這邊也來快速聊一下這一塊。
首先我們先來一段 JavaScript 很常見的物件傳參考的基本範例程式碼:
var b = {
name: 'Ray',
};
var e = b;
e.name = 'QQ';
console.log(b.name); // QQ
上面的觀念我們都知道這是一個物件傳參考概念,如果對於這一段有興趣的話可以閱讀我這一篇文章「JavaScript 核心觀念(27)-物件-物件參考觀念的實際運作模式」會有更詳細的說明,因此這邊就不著墨了(笑)。
前面講那麼多,那 Python 的狀況又如何呢?我們將上面的 JavaScript 範例程式碼直接改寫成 Python 版本試試看:
b = {
'name': 'Ray'
}
e = b
e['name'] = 'QQ'
print(b['name']) # QQ
好吧...依照輸出的結果來看, Python 的字典跟 JavaScript 的物件傳參考狀況非常像沒有錯吧~
接下來讓我們看看另一個範例程式碼:
def fn (x):
x['name'] = 'QQ'
b = {
'name': 'Ray'
}
fn(b)
print(b['name']) # QQ
上面這個範例程式碼確實沒有任何問題,感覺也是物件傳參考有非常大的關聯性,但是如果範例程式碼變成了以下,那麼結果會是怎樣呢?
def fn (x):
x = {
'name': 'Ray'
}
b = {
'name': 'Ray'
}
fn(b)
print(b['name']) # Ray
疑?奇怪了,為什麼結果不一樣了?
好吧,這邊要講一下關於物件(字典)傳參考的時候會有一個很特別的地方,雖然我們知道 Python 的字典基本上與 JavaScript 非常雷同,因此再將字典賦予到變數時,其實是賦予一個記憶體空間位置,我知道這邊有點難懂,所以我想換個方式形容,我們試著把容器型別與數值型別、字串型別以生活化的角度去理解看看。
而這邊會用車子去當作舉例,因此數值型別與字串型別你可以把它想像成是車子內的某些東西,例如:輪胎、方向盤,又或者收音機等等,字典與串列的話則是一個僅有車殼的車子(連引擎都沒有),所以透過這樣的舉例來講,可能就會像這樣:
# 把字典看成一個車殼,裡面放著車子的東西
car = {
'steeringWheel': 1, # 方向盤
'wheels': 4, # 輪胎
'radio': 1, # 收音機
}
那麼當我將 car
字典賦予到另一個變數時,其實概念類似我把車子借給他人使用,例如我借來了一台車:
car = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
ray = car
所以當我對著車子有任何調整,可能拆掉一個輪胎,那就會影響到原本的車子,畢竟車子是借來的:
car = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
ray = car
ray['wheels'] = 3
print(car['wheels']) # 3
那出借東西的行為就是 JavaScript 的物件傳參考,我只是借給你用而已的概念。
那麼有一種狀況下我們不會影響到原有的車子,也就是我後來終於有錢買自己的車子:
car = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
ray = car
ray = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
因為後來自己買了一台車子的關係,所以我就不再跟人家借車子,所以我接下來不管怎麼調整自己的車子都不會影響到原本 car
變數:
car = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
ray = car
ray = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
ray['radio'] = 0
print(car['radio']) # 1
上面的舉例只是希望讀者在閱讀時可以比較好理解,因此只要你看到變數被重新賦予一個字典 or 物件,就代表他會重新指向到一個新的記憶體空間(空車殼),如果這樣子還不好理解的話,以後你只要看到變數被重新賦予一個 {}
or []
就代表著它被重新指定了位址,因此就不會發生所謂的物件傳參考問題。
當然你也可以活用前面章節所學的 id
來查看彼此的記憶體空間是否相同:
car = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
ray = car
print(id(ray), id(car)) # 4463682048, 4463682048
如果是重新賦予的話,則是不同的記億體位置:
car = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
ray = car
ray = {
'steeringWheel': 1,
'wheels': 4,
'radio': 1,
}
print(id(ray), id(car)) # 4418249408 4418249216
透過前面章節所學的某些技巧也可以幫助到你理解,因此前面章節所分享的觀念都是非常重要的唷~
最後其實你應該會發現我一直刻意閃過一些東西不講,例如...call by xxx or pass by xxx 什麼的,最主要原因是我自己對於 Python 沒有非常的熟悉,所以自己也不敢多講什麼,主要也是怕講錯。
但是如果真的硬要問我的話,我可能會依據 wiki 所說的來講,因為以 wiki 的描述來講,Python 是比較接近 Call by sharing
傳共享物件呼叫(Call by sharing)的方式由Barbara Liskov命名,並被Python、Java(物件類型)、JavaScript、Scheme、OCaml等語言使用。
而這一點也剛好跟 JavaScript 非常像,因此我自己覺得你想把 Python 看成 JavaScript 似乎也可以?!
最後的最後也想聊個好玩的東西,也就是以下程式碼:
a = 'Ray'
b = 'Ray'
print('a', a, id(a)) # a Ray 4351631984
print('b', b, id(b)) # b Ray 4351631984
上面你可以發現我並沒有 a = b
,但是兩者的記憶體位置卻是相同,因此代表著字串在建立時都是指向同一個記憶體位置,也因此才會導致 a == b
是一個 True
,而這一段我覺得可以理解下面這張圖:
但是如果重新賦予其他值的時候,就會是直接指向到另一個記憶體位置:
如果是字典的話,那麼也是類似,但是字典則是看到一個新的 {}
or []
才會重新指向,這邊就讓我偷懶一下用之前 「JavaScript 核心觀念(27)-物件-物件參考觀念的實際運作模式」的文章圖片偷懶一下:
那今天也差不多介紹到這邊就告一個段落囉~
中秋節那段時間因為回老家團圓的關係,所以訊號一直都很差,幾乎只能打電話不能上網的等級,所以中秋節的晚上我都只能站在馬路正中央收發訊息,收發完就要快點閃離馬路,不然我現在應該不會在這邊寫文章了...